package com.rengwuxian.coursecoroutines._2_structured_concurrency

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.coroutines.EmptyCoroutineContext

/**
 * 不配合取消：NonCancellable
 * Job.cancel()、协程的结构化取消
 *
 * 前提：协程的取消是结构化的，父协程取消，所有子协程也会被取消。
 *
 * 在子协程包一个NonCancellable,父协程取消，就不会取消这个子协程了。
 * val parentJob = scope.launch {
 *     childJob = launch(NonCancellable) {
 *       println("Child started")
 *       delay(1000)
 *       println("Child stopped")
 *     }
 * }
 * parentJob.cancel()
 * 但是直接取消子协程 childJob.cancel()，仍然是可以正常被取消的。
 *
 * 原理：NonCancellable ：是一个单例Job，切断了内外协程的父子关系。且它自己也不是内部协程的父协程，child和parent都是空的。
 *
 * 没有处理过CancellationException异常的挂起函数，比如下面无用的挂起函数uselessSuspendFun(),是不会抛出异常的，协程的取消是交互式的。
 *
 * 在不希望取消的协程中，如果协程被cancel，收尾工作中有正常的挂起函数，不希望被打断怎么办？
 * withContext(NonCancellable) { //this code will not be cancelled }
 * withContext() {} 本身是一个可以被取消的挂起函数。
 *
 * withContext(Dispatchers.IO + NonCancellable) {
 *   // write to file
 *   // read from database (Room)
 *   // write data to file
 * }
 *
 * 作用：
 * 什么场景下，不希望协程被父协程取消（用NonCancellable切断父子关系链条）
 * 1.取消前的收尾工作：清理工作，协程被取消前的收尾代码。（比如写数据库）
 *   withContext(NonCancellable) { //this code will not be cancelled }
 *   withContext(NonCancellable) {
 *           // Write to database (Room)
 *           delay(1000)
 *   }
 * 2.不好收尾不想取消的工作：有些操作要么不开始，要么就完成。（比如文件读写等）属于业务上的抉择。不想花时间在取消工作上。
 *   withContext(Dispatchers.IO + NonCancellable) {
 *    // write to file
 *    // read from database (Room)
 *    // write data to file
 *   }
 * 3.协程做的事儿，跟当前协程流程无关的工作。跟当前业务无关的其他业务工作：比如日志，埋点。
 *   通常使用：
 *   launch(NonCancellable) {
 *         // Log
 *   }
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  var childJob: Job? = null
  var childJob2: Job? = null
  val newParent = Job()
  val parentJob = scope.launch {
    childJob = launch(NonCancellable) {
      println("Child started")
      delay(1000)
      println("Child stopped")
    }
    println("childJob parent: ${childJob?.parent}")
    childJob2 = launch(newParent) {
      println("Child started")
      writeInfo()
      launch(NonCancellable) {
        // Log
      }
      if (!isActive) {
        withContext(NonCancellable) {
          // Write to database (Room)
          delay(1000)
        }
        throw CancellationException()
      }
      try {
        delay(3000)
      } catch (e: CancellationException) {

        throw e
      }
      println("Child 2 started")
      delay(3000)
      println("Child 2 stopped")
    }
    println("Parent started")
    delay(3000)
    println("Parent stopped")
  }
  delay(1500)
  newParent.cancel()
  delay(10000)
}

suspend fun writeInfo() = withContext(Dispatchers.IO + NonCancellable) {
  // write to file
  // read from database (Room)
  // write data to file
}

suspend fun uselessSuspendFun() {
  Thread.sleep(1000)
}